﻿#region Using
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Linq2Crm.Interpreter;
using Linq2Crm.ObjectModel;
using Linq2Crm.ObjectModel.DomainObject;
using Linq2Crm.Request;
using Linq2Crm.Request.SpecificRequest;
using SilverCrmSoap.CrmSdk;

#endregion

namespace Linq2Crm
{
	public sealed partial class DomainObjectContext
	{
		public int GetRecordsSeed { get; set; }

		internal async Task<ResponseBase> ExecuteGetRequestAsync(GetRequest request, IProgress<int> reportProgress = null)
		{
			if (!request.BackgroundTask)
			{
				IncrementCounter();
			}

			GetResponse response = new GetResponse();
			ExpressionInterpreter interpreter = new ExpressionInterpreter();
			ExpressionResult interpretedResult;
			try
			{
				interpretedResult = interpreter.LinQToQueryExpression(request.Query.Expression);
			}
			catch (Exception e)
			{
				response.Error = e;
				DecrementCounter();
				return response;
			}

			QueryExpression multiQuery = interpretedResult.QueryExpression;

			//check if there's values in "Contain" and if nothing then return nothing since there wouldn't be any record to match non-existing filter
			if (!IsProperFilter(multiQuery.Criteria))
			{
				response = new GetResponse
					           {
						           Successful = true,
						           Items = new List<IDomainObject>()
					           };
				DecrementCounter();
				return response;
			}

			List<string> entities = new List<string>
				                        {
					                        multiQuery.EntityName
				                        };
			entities.AddRange(GetLinkedEntitiesNames(multiQuery.LinkEntities));

			#region getting view's query expression
			if (request.UseView != null)
			{
				QueryExpression viewExpression = await GetQueryExpressionForView(request.UseView);
				if (viewExpression == null)
				{
					response.Successful = false;
					response.Error = new Exception("Cannot get view");
					DecrementCounter();
					return response;
				}

				#region MergeQueryExpressions
				try
				{
					IEnumerable<string> allColumns = viewExpression.ColumnSet.Columns.Union(multiQuery.ColumnSet.Columns);
					multiQuery.ColumnSet = new ColumnSet();
					multiQuery.ColumnSet.Columns = new ObservableCollection<string>(allColumns.ToArray());

					if (multiQuery.LinkEntities == null)
					{
						multiQuery.LinkEntities = new ObservableCollection<LinkEntity>();
					}
					foreach (LinkEntity linkedEntity in viewExpression.LinkEntities)
					{
						multiQuery.LinkEntities.Add(linkedEntity);
					}
					if (multiQuery.Criteria == null)
					{
						multiQuery.Criteria = new FilterExpression();
					}
					if (multiQuery.Criteria.Conditions == null)
					{
						multiQuery.Criteria.Conditions = new ObservableCollection<ConditionExpression>();
					}
					foreach (ConditionExpression condition in viewExpression.Criteria.Conditions)
					{
						multiQuery.Criteria.Conditions.Add(condition);
					}
					if (multiQuery.Criteria.Filters == null)
					{
						multiQuery.Criteria.Filters = new ObservableCollection<FilterExpression>();
					}
					foreach (FilterExpression filter in viewExpression.Criteria.Filters)
					{
						multiQuery.Criteria.Filters.Add(filter);
					}
				}
				catch (Exception e)
				{
					Debug.Assert(false, e.Message);
				}
				#endregion
			}
			#endregion

			Exception ex = await ExecuteWithAttributeRequestAsync(entities.ToArray());

			if (ex != null)
			{
				response = new GetResponse
					           {
						           Error = ex,
						           Successful = false
					           };
				DecrementCounter();
				return response;
			}

			foreach (string column in multiQuery.ColumnSet.Columns)
			{
				if (_attributesList[multiQuery.EntityName].Attributes.Any(x => x.LogicalName.Equals(column)))
				{
					continue;
				}
				response.Error = new ArgumentOutOfRangeException("column", string.Format("Entity {1} doesn't contain column with name {0}", column, multiQuery.EntityName));
				response.Successful = false;
				DecrementCounter();
				return response;
			}

			try
			{
				bool useSpecificPagination = interpretedResult.Skip != 0 || interpretedResult.Take != 0;
				multiQuery.PageInfo = new PagingInfo
					                      {
						                      Count = GetRecordsSeed,
						                      PageNumber = 1,
						                      ReturnTotalRecordCount = true
					                      };

				//If we a user want to skip more than 250 records -> there is no need to start from the 1st page. 
				//Start with the page where needed records begin
				multiQuery.PageInfo.PageNumber = (interpretedResult.Skip/GetRecordsSeed) + 1;

				int skip = interpretedResult.Skip%GetRecordsSeed;
				int totalCountWithTakeCommand = interpretedResult.Take;

				while (true)
				{
					EntityCollection collection = await Task.Factory.FromAsync<QueryBase, EntityCollection>(
						OrganizationService.BeginRetrieveMultiple,
						OrganizationService.EndRetrieveMultiple,
						multiQuery,
						null);

					DomainObjectFactory domF = new DomainObjectFactory(_attributesList[collection.EntityName].Attributes);
					foreach (Entity entity in collection.Entities)
					{
						IDomainObject item = domF.CreateDomainObjectFrom(entity, request.DomainObjectType);
						FillLinkedEntities(item, entity);
						response.Items.Add(item);
					}

					if (useSpecificPagination)
					{
						//Skip the rest of unneeded records (only 1 time during first get call)
						if (skip > 0)
						{
							response.Items = response.Items.Skip(skip).ToList();
							skip = 0;
						}
						//When we reached our take count -> take only number of records user asked and return.
						//There is no need to contrinue reading records
						if (totalCountWithTakeCommand > 0 && response.Items.Count >= totalCountWithTakeCommand)
						{
							response.Items = response.Items.Take(totalCountWithTakeCommand).ToList();
							break;
						}
					}

					if (collection.TotalRecordCount != 5000 && collection.TotalRecordCount <= response.Items.Count)
					{
						break;
					}
					if (collection.Entities.Count <= GetRecordsSeed && String.IsNullOrEmpty(collection.PagingCookie))
					{
						break;
					}
					multiQuery.PageInfo.PagingCookie = collection.PagingCookie;
					multiQuery.PageInfo.PageNumber++;

					if (reportProgress != null)
					{
						reportProgress.Report(response.Items.Count*100/(collection.TotalRecordCount != 5000
							                                                ? collection.TotalRecordCount
							                                                : collection.TotalRecordCount + response.Items.Count/2)); //just to show that there can be more records
					}
				}

				response.Successful = true;
			}
			catch (Exception exception)
			{
				Debug.Assert(false);
				response.Error = exception;
				response.Successful = false;
			}

			if (!request.BackgroundTask)
			{
				DecrementCounter();
			}
			return response;
		}

		private async Task<QueryExpression> GetQueryExpressionForView(View view)
		{
			OrganizationRequest orgR = new OrganizationRequest
				                           {
					                           RequestName = "FetchXmlToQueryExpression",
					                           Parameters = new ParameterCollection
						                                        {
							                                        new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
								                                        {
									                                        Key = "FetchXml",
									                                        Value = view.FetchXml
								                                        }
						                                        }
				                           };

			OrganizationRequestWrapper orgRequest = OrganizationRequestWrapper.Create(orgR);
			OrganizationResponseWrapper orgResponse = (OrganizationResponseWrapper) (await ExecuteOrganizationRequestAsync(orgRequest));

			if (!orgResponse.Successful)
			{
				return null;
			}
			return (QueryExpression) orgResponse.Results.First().Value;
		}

		internal async Task<ResponseBase> ExecuteInsertRequestAsync(InsertRequest request)
		{
			IncrementCounter();
			InsertResponse response = new InsertResponse();

			AttributeParser ap = new AttributeParser(request.DomainObjectType);
			AnalyzeResult analyzeResults = ap.AnalyzeType();
			Exception ex = await ExecuteWithAttributeRequestAsync(analyzeResults.EntityName);

			if (ex != null)
			{
				response.Successful = false;
				response.Error = ex;
				DecrementCounter();
				return response;
			}

			DomainObjectFactory domF = new DomainObjectFactory(_attributesList[analyzeResults.EntityName].Attributes);
			try
			{
				Entity entity = domF.CreateCrmEntityFrom(request.Entity, CheckType.Inserting);

				Guid newId = await Task.Factory.FromAsync<Entity, Guid>(OrganizationService.BeginCreate, OrganizationService.EndCreate, entity, null);
				request.Entity.Id = newId;
				response.Id = newId;
				request.Entity.StateCode = (int) RecordStateCode.Active;
				request.Entity.OrigStatusCode = request.Entity.StatusCode = domF.GetDefaultStatuses() == null
					                                                            ? (int) RecordStateCode.Active
					                                                            : domF.GetDefaultStatuses()[RecordStateCode.Active];

				response.Successful = true;
			}
			catch (Exception exception)
			{
				response.Successful = false;
				response.Error = exception;
			}

			DecrementCounter();
			return response;
		}

		internal async Task<ResponseBase> ExecuteDeleteRequestAsync(DeleteRequest request)
		{
			IncrementCounter();
			DeleteResponse response = new DeleteResponse();

			if (request.Entity.OrigStatusCode == null)
			{
				response.Successful = true;
				DecrementCounter();
				return response;
			}

			AttributeParser ap = new AttributeParser(request.DomainObjectType);
			AnalyzeResult analyze = ap.AnalyzeType();

			switch (request.DeleteMethod)
			{
				case DeleteRequest.DeleteType.ChangeStatus:

					Exception ex = await ExecuteWithAttributeRequestAsync(analyze.EntityName);

					if (ex != null)
					{
						response = new DeleteResponse
							           {
								           Error = ex,
								           Successful = false
							           };
						DecrementCounter();
						return response;
					}

					DomainObjectFactory domF = new DomainObjectFactory(_attributesList[analyze.EntityName].Attributes);
					Dictionary<RecordStateCode, int> statuses = domF.GetDefaultStatuses();
					SetStatusRequest ss = new SetStatusRequest(request.DomainObjectType, RecordStateCode.Inactive, statuses[RecordStateCode.Inactive]);
					ss.Entity = request.Entity;

					ResponseBase setStatusResponse = await ExecuteSetStatusRequestAsync(ss);

					response = new DeleteResponse
						           {
							           Error = setStatusResponse.Error,
							           Successful = setStatusResponse.Successful
						           };
					DecrementCounter();
					return response;
				case DeleteRequest.DeleteType.Delete:
					try
					{
						await Task.Factory.FromAsync(OrganizationService.BeginDelete, OrganizationService.EndDelete, analyze.EntityName, request.Entity.Id, null);
						response.Successful = true;
					}
					catch (Exception exception)
					{
						response.Error = exception;
						response.Successful = false;
					}
					DecrementCounter();
					return response;
				default:
					Debug.Assert(false, "Invalid DeleteType");
					response.Successful = false;
					response.Error = new ArgumentOutOfRangeException("Invalid DeleteType");
					return response;
			}
		}

		internal async Task<ResponseBase> ExecuteSetStatusRequestAsync(SetStatusRequest request)
		{
			IncrementCounter();
			SetStatusResponse response = new SetStatusResponse();
			AttributeParser ap = new AttributeParser(request.DomainObjectType);
			AnalyzeResult analyze = ap.AnalyzeType();

			DomainObjectFactory domF = new DomainObjectFactory(_attributesList[analyze.EntityName].Attributes);
			Dictionary<RecordStateCode, Dictionary<int, string>> list = domF.GetStatusList();

			bool needStateRequest = false;

			if (request.Entity.StatusCode != -1 && request.Entity.OrigStatusCode != request.StatusCode && list.Any(x => x.Value.Any(z => z.Key == request.StatusCode)))
			{
				request.StateCode = (int) list.FirstOrDefault(x => x.Value.ContainsKey(request.StatusCode)).Key;
				needStateRequest = true;
			}
			else //if stateCode has changed but status was not
			{
				if (request.Entity.StateCode == RecordStateCode.Active && !list[RecordStateCode.Active].Any(x => x.Key == request.Entity.OrigStatusCode))
				{
					request.StateCode = (int) RecordStateCode.Active;
					request.StatusCode = -1;
					needStateRequest = true;
				}
				else if (request.Entity.StateCode == RecordStateCode.Inactive && !list[RecordStateCode.Inactive].Any(x => x.Key == request.Entity.OrigStatusCode))
				{
					request.StateCode = (int) RecordStateCode.Inactive;
					request.StatusCode = -1;
					needStateRequest = true;
				}
			}

			//if statuscode is not exist
			if (request.StatusCode != -1 && !list.Any(x => x.Value.Any(z => z.Key == request.StatusCode)))
			{
				response.Successful = false;
				response.Error = new ArgumentOutOfRangeException(
					"StatusCode", string.Format("StatusOptionSet doesn't contain StatusCode ({0}) for specified State({1})", request.StatusCode, request.StateCode));

				DecrementCounter();
				return response;
			}

			if (needStateRequest)
			{
				OrganizationRequest sRequest = new OrganizationRequest
					                               {
						                               RequestName = "SetState",
						                               Parameters = new ParameterCollection()
					                               };
				sRequest["EntityMoniker"] = new EntityReference
					                            {
						                            LogicalName = analyze.EntityName,
						                            Id = request.Entity.Id
					                            };
				sRequest["State"] = new OptionSetValue
					                    {
						                    Value = request.StateCode
					                    };
				sRequest["Status"] = new OptionSetValue
					                     {
						                     Value = request.StatusCode
					                     };

				OrganizationService.BeginExecute(sRequest, ExecuteSetStatusRequestCompleted, request);

				try
				{
					OrganizationResponse setStateResponse = await Task.Factory.FromAsync<OrganizationRequest, OrganizationResponse>(OrganizationService.BeginExecute, OrganizationService.EndExecute, sRequest, null);

					request.Entity.StatusCode = request.StatusCode;
					request.Entity.OrigStatusCode = request.StatusCode;

					response.Successful = true;
				}
				catch (Exception ex)
				{
					response.Error = ex;
					response.Successful = false;
				}
				DecrementCounter();

				return response;
			}
			else
			{
				response = new SetStatusResponse
					           {
						           Successful = true
					           };
				DecrementCounter();

				return response;
			}
		}

		internal async Task<ResponseBase> ExecuteUpdateRequestAsync(UpdateRequest request)
		{
			IncrementCounter();
			UpdateResponse response = new UpdateResponse();

			if (request.Entity.OrigStatusCode == null)
			{
				#region Insert with state change
				RecordStateCode stateCode = request.Entity.StateCode;
				int statusCode = request.Entity.StatusCode;

				InsertRequest ir = new InsertRequest(request.DomainObjectType, request.Entity);

				ResponseBase iResponse = await ExecuteInsertRequestAsync(ir);

				if (!iResponse.Successful)
				{
					response = new UpdateResponse
						           {
							           Error = iResponse.Error,
							           Successful = iResponse.Successful
						           };
					DecrementCounter();
					return response;
				}
				else
				{
					SetStatusRequest sStatusRequest = new SetStatusRequest(request.DomainObjectType, stateCode, statusCode)
						                                  {
							                                  Entity = request.Entity,
						                                  };

					ResponseBase setStatusResponse = await ExecuteSetStatusRequestAsync(sStatusRequest);
					response = new UpdateResponse
						           {
							           Error = setStatusResponse.Error,
							           Successful = setStatusResponse.Successful
						           };
					DecrementCounter();

					return response;
				}
				#endregion
			}

			//First - Update, then - change state
			AttributeParser ap = new AttributeParser(request.DomainObjectType);
			AnalyzeResult analyzeResults = ap.AnalyzeType();
			Exception ex = await ExecuteWithAttributeRequestAsync(analyzeResults.EntityName);

			if (ex != null)
			{
				response = new UpdateResponse
					           {
						           Error = ex,
						           Successful = false
					           };
				DecrementCounter();
				return response;
			}

			DomainObjectFactory domF = new DomainObjectFactory(_attributesList[analyzeResults.EntityName].Attributes);
			Entity entity = domF.CreateCrmEntityFrom(request.Entity, CheckType.Updating);
			try
			{
				await Task.Factory.FromAsync(OrganizationService.BeginUpdate, OrganizationService.EndUpdate, entity, null);
				response.Successful = true;
			}
			catch (Exception e)
			{
				response.Error = e;
				response.Successful = false;
			}

			if (response.Successful)
			{
				SetStatusRequest setStatusRequest = new SetStatusRequest(request.DomainObjectType, request.Entity.StateCode, request.Entity.StatusCode);
				setStatusRequest.Entity = request.Entity;

				ResponseBase setStatusResponse = await ExecuteSetStatusRequestAsync(setStatusRequest);
				response = new UpdateResponse
					           {
						           Error = setStatusResponse.Error,
						           Successful = setStatusResponse.Successful
					           };
			}
			DecrementCounter();
			return response;
		}

		internal async Task<ResponseBase> ExecuteRelationshipRequestAsync(ManyToManyRelationshipRequest request)
		{
			IncrementCounter();
			ResponseBase relationResponse;

			if (!_manyToManyRelationshipMetadatas.ContainsKey(request.ManyToManyRelationshipName))
			{
				OrganizationRequest innerRequest = new OrganizationRequest();
				innerRequest.RequestName = "RetrieveRelationship";
				innerRequest.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
					                            {
						                            Key = "Name",
						                            Value = request.ManyToManyRelationshipName
					                            });
				innerRequest.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
					                            {
						                            Key = "MetadataId",
						                            Value = Guid.Empty
					                            });
				innerRequest.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
					                            {
						                            Key = "RetrieveAsIfPublished",
						                            Value = false
					                            });

				OrganizationResponse organizationResponse = null;
				try
				{
					organizationResponse = await Task.Factory.FromAsync<OrganizationRequest, OrganizationResponse>(
						OrganizationService.BeginExecute, OrganizationService.EndExecute, innerRequest, null);
				}
				catch (Exception ex)
				{
					DecrementCounter();
					relationResponse = new ManyToManyRelationshipResponse
						                   {
							                   Error = ex,
							                   Successful = false
						                   };
					return relationResponse;
				}

				if (organizationResponse.Results.Count == 0 && !(organizationResponse.Results[0].Value is ManyToManyRelationshipMetadata))
				{
					DecrementCounter();
					relationResponse = new ManyToManyRelationshipResponse
						                   {
							                   Error = new ArgumentException("Metadata doesn't belong to ManyToMany relationship"),
							                   Successful = false
						                   };
					return relationResponse;
				}

				ManyToManyRelationshipMetadata data = (ManyToManyRelationshipMetadata) organizationResponse.Results[0].Value;
				if (!_manyToManyRelationshipMetadatas.ContainsKey(request.ManyToManyRelationshipName))
				{
					_manyToManyRelationshipMetadatas.Add(request.ManyToManyRelationshipName, data);
				}

				relationResponse = await GetRelationShipAsync(request, data);
			}
			else
			{
				relationResponse = await GetRelationShipAsync(request, _manyToManyRelationshipMetadatas[request.ManyToManyRelationshipName]);
			}

			DecrementCounter();
			return relationResponse;
		}

		internal async Task<ResponseBase> ExecuteOrganizationRequestAsync(OrganizationRequestWrapper request)
		{
			IncrementCounter();
			OrganizationResponseWrapper response = new OrganizationResponseWrapper();

			try
			{
				OrganizationResponse organizationResponse = await Task.Factory.FromAsync<OrganizationRequest, OrganizationResponse>(
					OrganizationService.BeginExecute, OrganizationService.EndExecute, request.OrganizationRequest, null);

				response.Results = organizationResponse.Results.ToDictionary(x => x.Key, z => z.Value);
				response.Successful = true;
			}
			catch (Exception ex)
			{
				response.Error = ex;
				response.Successful = false;
			}
			DecrementCounter();
			return response;
		}

		internal async Task<ResponseBase> ExecuteSettingsRequestAsync(SettingsRequest request)
		{
			IncrementCounter();
			SettingsResponse response = new SettingsResponse();

			OrganizationRequest oRequest = new OrganizationRequest
				                               {
					                               RequestName = "RetrieveUserSettingsSystemUser",
					                               Parameters = new ParameterCollection()
				                               };
			oRequest.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
				                        {
					                        Key = "EntityId",
					                        Value = request.UserId
				                        });
			oRequest.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
				                        {
					                        Key = "ColumnSet",
					                        Value = new ColumnSet
						                                {
							                                AllColumns = true,
							                                Columns = new ObservableCollection<string>
								                                          {
									                                          ""
								                                          }
						                                }
				                        });

			try
			{
				OrganizationResponse organizationResponse =
					await Task.Factory.FromAsync<OrganizationRequest, OrganizationResponse>(OrganizationService.BeginExecute, OrganizationService.EndExecute, oRequest, null);

				SilverCrmSoap.CrmSdk.KeyValuePair<string, object> settingsresponse = organizationResponse.Results.First();
				Entity rEntity = settingsresponse.Value as Entity;

				if (rEntity == null)
				{
					throw new Exception("Cannot get user settings");
				}

				foreach (SilverCrmSoap.CrmSdk.KeyValuePair<string, object> attribute in rEntity.Attributes)
				{
					response.UserSettings.Add(attribute.Key, attribute.Value);
				}
				response.Successful = true;
			}
			catch (Exception e)
			{
				response.Error = e;
				response.Successful = false;
			}

			DecrementCounter();
			return response;
		}

		internal async Task<ResponseBase> ExecuteWhoAmIRequestAsync(WhoAmIRequestWrapper request)
		{
			IncrementCounter();
			WhoAmIResponseWrapper response = new WhoAmIResponseWrapper();

			try
			{
				OrganizationResponse whoAmIResponse = await Task.Factory.FromAsync<OrganizationRequest, OrganizationResponse>(
					OrganizationService.BeginExecute, OrganizationService.EndExecute, request.WhoAmIRequest, null);

				response.BusinessUnitId = (Guid) whoAmIResponse["BusinessUnitId"];
				response.OrganizationId = (Guid) whoAmIResponse["OrganizationId"];
				response.UserId = (Guid) whoAmIResponse["UserId"];

				SettingsRequest sr = SettingsRequest.Create(response.UserId);
				SettingsResponse settingsResponse = (SettingsResponse) await ExecuteSettingsRequestAsync(sr);

				response.Successful = true;
				response.UserSettings = settingsResponse.UserSettings;

				if (!settingsResponse.Successful)
				{
					response.Error = settingsResponse.Error;
					response.Successful = false;
				}
			}
			catch (Exception ex)
			{
				response.Error = ex;
				response.Successful = false;
			}

			DecrementCounter();
			return response;
		}

		private async Task<Exception> ExecuteWithAttributeRequestAsync(params string[] entityNames)
		{
			if (entityNames == null || !entityNames.Any())
			{
				return null;
			}

			string entName = entityNames.First();
			if (!_attributesList.ContainsKey(entName))
			{
				Exception exception = await RetrieveAttributeListAsync(entName);
				if (exception != null)
				{
					return exception;
				}

				return await ExecuteWithAttributeRequestAsync(entityNames.Skip(1).ToArray());
			}

			return await ExecuteWithAttributeRequestAsync(entityNames.Skip(1).ToArray());
		}

		private async Task<Exception> RetrieveAttributeListAsync(string entity)
		{
			IncrementCounter();

			OrganizationRequest request = new OrganizationRequest
				                              {
					                              RequestName = "RetrieveEntity",
					                              Parameters = new ParameterCollection()
				                              };
			request.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
				                       {
					                       Key = "EntityFilters",
					                       Value = EntityFilters.Attributes
				                       });
			request.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
				                       {
					                       Key = "LogicalName",
					                       Value = entity
				                       });
			request.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
				                       {
					                       Key = "MetadataId",
					                       Value = Guid.Empty
				                       });
			request.Parameters.Add(new SilverCrmSoap.CrmSdk.KeyValuePair<string, object>
				                       {
					                       Key = "RetrieveAsIfPublished",
					                       Value = false
				                       });

			try
			{
				OrganizationResponse organizationResponse = await Task.Factory.FromAsync<OrganizationRequest, OrganizationResponse>(
					OrganizationService.BeginExecute,
					OrganizationService.EndExecute,
					request,
					null);

				if (organizationResponse.Results.Any())
				{
					if (!_attributesList.ContainsKey(entity))
					{
						_attributesList.Add(entity, ((EntityMetadata) organizationResponse.Results.First().Value));
					}
				}
			}
			catch (Exception e)
			{
				DecrementCounter();
				return e;
			}

			DecrementCounter();
			return null;
		}

		private async Task<ResponseBase> GetRelationShipAsync(ManyToManyRelationshipRequest request, ManyToManyRelationshipMetadata data)
		{
			QueryExpression query = new QueryExpression
				                        {
					                        Criteria = new FilterExpression
						                                   {
							                                   Conditions = new ObservableCollection<ConditionExpression>()
						                                   }
				                        };

			if (request.FEntityFilter != null && request.FEntityFilter.Any())
			{
				ConditionExpression condition = new ConditionExpression();
				condition.AttributeName = data.Entity1IntersectAttribute;
				condition.Operator = ConditionOperator.In;
				condition.Values = new ObservableCollection<object>(request.FEntityFilter.Cast<object>());
				query.Criteria.Conditions.Add(condition);
			}
			if (request.SEntityFilter != null && request.SEntityFilter.Any())
			{
				ConditionExpression condition = new ConditionExpression();
				condition.AttributeName = data.Entity2IntersectAttribute;
				condition.Operator = ConditionOperator.In;
				condition.Values = new ObservableCollection<object>(request.SEntityFilter.Cast<object>());
				query.Criteria.Conditions.Add(condition);
			}
			query.EntityName = data.IntersectEntityName;
			query.ColumnSet = new ColumnSet
				                  {
					                  AllColumns = true,
					                  Columns = new ObservableCollection<string>()
				                  };
			request.InnerMetadata = data;

			ManyToManyRelationshipResponse response = new ManyToManyRelationshipResponse();
			try
			{
				EntityCollection entityCollection = await Task.Factory.FromAsync<QueryBase, EntityCollection>(
					OrganizationService.BeginRetrieveMultiple, OrganizationService.EndRetrieveMultiple, query, null);

				foreach (Entity entity in entityCollection.Entities)
				{
					Guid key = entity.GetAttributeValue<Guid>(request.InnerMetadata.Entity1IntersectAttribute);
					if (!response.Results.ContainsKey(key))
					{
						response.Results.Add(key, new List<Guid>());
					}
					response.Results[key].Add(entity.GetAttributeValue<Guid>(request.InnerMetadata.Entity2IntersectAttribute));
				}
				response.Successful = true;
			}
			catch (Exception ex)
			{
				response.Error = ex;
				response.Successful = false;
			}

			return response;
		}

		internal async Task<GetViewResponse> ExecuteGetViewRequest(GetViewRequest request)
		{
			IncrementCounter();
			GetViewResponse response = new GetViewResponse();

			AttributeParser apEntity = new AttributeParser(request.DomainObjectType);
			apEntity.AnalyzeType();

			//NOTE: from Oleg: x.QueryType == 0 is taken because of http://msdn.microsoft.com/en-us/library/gg309339.aspx
			// main app views always will have query type equals zero

			GetRequest getSystemView = GetRequest.Create<SystemView>();
			string entityName = apEntity.AnalyzeResult.EntityName.ToLower();
			getSystemView.Query = RequestBase.CreateQuery<SystemView>().Where(x => x.StateCode == RecordStateCode.Active && x.ReturnedTypeCode == entityName && x.QueryType == 0);
			GetResponse getSystemViewResponse = (GetResponse) (await ExecuteGetRequestAsync(getSystemView));
			if (!getSystemViewResponse.Successful)
			{
				response.Error = getSystemViewResponse.Error;
				response.Successful = false;
				DecrementCounter();
				return response;
			}
			IEnumerable<View> systemViews = getSystemViewResponse.Items.Cast<View>();

			if (request.UseUserQuery)
			{
				GetRequest getUserView = GetRequest.Create<SavedView>();
				getUserView.Query = RequestBase.CreateQuery<SavedView>().Where(x => x.StateCode == RecordStateCode.Active && x.ReturnedTypeCode == entityName && x.QueryType == 0);
				GetResponse getSavedView = (GetResponse) (await ExecuteGetRequestAsync(getUserView).ConfigureAwait(true));
				if (!getSavedView.Successful)
				{
					response.Error = getSavedView.Error;
					response.Successful = false;
					DecrementCounter();
					return response;
				}
				response.Views = getSavedView.Items.Cast<View>().Concat(systemViews);
			}
			else
			{
				response.Views = systemViews;
			}
			response.Successful = true;
			DecrementCounter();
			return response;
		}
	}
}